﻿//#define XAMLTYPE_TRACE

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using System.Windows.Markup;
using System.Xaml;
using System.Xaml.Schema;

using alib;
using alib.Array;
using alib.Debugging;
using alib.Enumerable;
using alib.Reflection;
using alib.String;

namespace agree.schema.xaml
{
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// 
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public abstract class xaml_type : XamlType
	{
		const String xaml_2006 = "http://schemas.microsoft.com/winfx/2006/xaml";

		public xaml_type(schema_type st)
			: base(st, st.sch.xsch)
		{
			this.st = st;
			this.sch = st.sch;
			this.f_namescope = typeof(INameScope).IsAssignableFrom(st);
			this.f_top_down = -1;

			trace_newobj();
		}

		public readonly Schema sch;
		public readonly schema_type st;
		readonly bool f_namescope;
		int f_top_down;

		protected sealed override Type LookupUnderlyingType() { return st; }

		protected sealed override bool LookupIsNameScope() { return f_namescope; }

		protected sealed override bool LookupIsWhitespaceSignificantCollection() { return false; }

		protected sealed override bool LookupUsableDuringInitialization()
		{
			if (f_top_down == -1)
				this.f_top_down = base.LookupUsableDuringInitialization() ? 1 : 0;
			return f_top_down == 1;
		}

		protected sealed override XamlMember LookupAliasedProperty(XamlDirective d)
		{
			if (d.PreferredXamlNamespace == xaml_2006 && d.Name == "Name")
				return sch.GetXamlAttachedProp(d.Name);

			return base.LookupAliasedProperty(d);
		}

		public override bool CanAssignTo(XamlType xt_other)
		{
			Type t_other;
			if ((Object)xt_other == null || (t_other = xt_other.UnderlyingType) == null)
				return false;

			return sch.promote_type(t_other).IsAssignableFrom(st);
		}

#if XAMLTYPE_TRACE
		protected override XamlCollectionKind LookupCollectionKind()
		{
			var x = base.LookupCollectionKind();
			Debug.Print("{0,25}.LookupCollectionKind: {1}", "[" + this.Name + "]", x);
			return x;
		}
		protected override XamlType LookupItemType()
		{
			var x = base.LookupItemType();
			Debug.Print("{0,25}.LookupItemType: {1}", "[" + this.Name + "]", x);
			return x;
		}
		protected override IList<XamlType> LookupAllowedContentTypes()
		{
			var x = base.LookupAllowedContentTypes();
			Debug.Print("{0,25}.LookupAllowedContentTypes: {1}", "[" + this.Name + "]", x == null ? "(null)" : x.StringJoin(", "));
			return x;
		}
		public override bool CanAssignTo(XamlType xamlType)
		{
			var x = base.CanAssignTo(xamlType);
			Debug.Print("{0,25}.CanAssignTo({1}): {2}", "[" + this.Name + "]", xamlType.Name, x);
			return x;
		}
		protected override IList<XamlType> LookupContentWrappers()
		{
			var x = base.LookupContentWrappers();
			Debug.Print("{0,25}.LookupContentWrappers: {1}", "[" + this.Name + "]", x == null ? "(null)" : x.StringJoin(", "));
			return x;
		}
		protected override XamlValueConverter<XamlDeferringLoader> LookupDeferringLoader()
		{
			var x = base.LookupDeferringLoader();
			Debug.Print("{0,25}.LookupDeferringLoader: {1}", "[" + this.Name + "]", x == null ? "(null)" : x.GetType()._Name(false));
			return x;
		}
		protected override EventHandler<XamlSetTypeConverterEventArgs> LookupSetTypeConverterHandler()
		{
			var x = base.LookupSetTypeConverterHandler();
			Debug.Print("{0,25}.LookupSetTypeConverterHandler: {1}", "[" + this.Name + "]", x == null ? "(null)" : x.GetType()._Name(false));
			return x;
		}
		protected override XamlValueConverter<TypeConverter> LookupTypeConverter()
		{
			var x = base.LookupTypeConverter();
			Debug.Print("{0,25}.LookupTypeConverter: {1}", "[" + this.Name + "]", x == null ? "(null)" : x.GetType()._Name(false));
			return x;
		}
#endif

		[Conditional("XAMLTYPE_TRACE")]
		void trace_newobj()
		{
			var moniker = base.GetType().Name.Replace("Agree", "").Replace("Type", "");
			moniker = moniker.Length == 0 ? "std" : moniker.Left(3).ToLower();
			//Debug.Print("define-" + moniker + "-type [" + t._Name(false) + "]");
			Debug.Print("define-" + moniker + "-type [" + st.Name + "]");
		}
	};


	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// 
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public class xaml_abstract_type : xaml_type
	{
		public xaml_abstract_type(schema_type st)
			: base(st)
		{
		}

		protected override bool LookupIsConstructible() { return false; }

		protected override XamlTypeInvoker LookupInvoker() { throw not.valid; }

		protected override bool LookupConstructionRequiresArguments() { throw not.valid; }

		protected override IList<XamlType> LookupPositionalParameters(int c) { throw not.valid; }

		protected override IEnumerable<XamlMember> LookupAllAttachableMembers() { throw not.valid; }

		protected override XamlMember LookupAttachableMember(String name) { throw not.valid; }
	};

	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// 
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public sealed class xaml_class_type : xaml_type
	{
		public xaml_class_type(schema_type st)
			: base(st)
		{
			XamlType xt_item;

			if ((xt_item = ItemType) == null)
				invoker = typeof(xaml_type_invoker)._ctor<xaml_type_invoker>(this);
			else
			{
				MethodInfo mi;
				var ti = sch.promote_type(xt_item.UnderlyingType);

				if ((mi = st.MultiAddMethod) == null)
					invoker = typeof(xaml_collection_invoker)._ctor<xaml_type_invoker>(this, ti);
				else
					invoker = typeof(xaml_multi_invoker)._ctor<xaml_type_invoker>(this, ti);
			}
		}

		readonly xaml_type_invoker invoker;

		protected override XamlTypeInvoker LookupInvoker() { return invoker; }

		protected override bool LookupIsConstructible() { return true; }

		protected override bool LookupConstructionRequiresArguments() { return false; }

		protected override IEnumerable<XamlMember> LookupAllAttachableMembers()
		{
			return sch.AllAttached.Select(spa => spa.XamlAttachedProperty);
		}

		protected override XamlMember LookupAttachableMember(String name)
		{
			return sch.GetXamlAttachedProp(name);
		}

		protected override IList<XamlType> LookupPositionalParameters(int c)
		{
			var x = base.LookupPositionalParameters(c + 1);
			return x.Skip(1).ToArray();
		}
	};


	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// 
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public class xaml_type_invoker : XamlTypeInvoker
	{
		public xaml_type_invoker(xaml_type a)
			: base(a)
		{
			this.sch = a.sch;
			this.xt = a;
			this.t = a.st;
		}

		protected readonly Schema sch;
		protected readonly xaml_type xt;
		protected readonly schema_type t;

		public override Object CreateInstance(Object[] args)
		{
			var xow = (xaml_obj_writer)args[0];

			arr.RemoveAt<Object>(ref args, 0);

			return xow.CreateInstance(t, args);
		}
	};

	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// 
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public class xaml_collection_invoker : xaml_type_invoker
	{
		public xaml_collection_invoker(xaml_type xt, Type t_item)
			: base(xt)
		{
			this.t_item = t_item;
		}
		readonly Type t_item;

		public override void AddToCollection(Object instance, Object item)
		{
			//Debug.Assert(instance.GetType() == base.t && instance != item);
			if (item == null)
				return;	// certain uses of 'PropertyRef' markup extension?

			if (item is SysObj || item is Array)
				return;

			Type Ti = sch.promote_type(item.GetType());
			PropertyInfo pi;
			if (item is Task && (pi = Ti.GetProperty("Result")) != null)
				item = pi.GetValue(item);

			base.AddToCollection(instance, item);
		}
	};

	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// 
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public class xaml_multi_invoker : xaml_type_invoker
	{
		public xaml_multi_invoker(xaml_type xt, Type t_item)
			: base(xt)
		{
			this.t_item = t_item;
		}
		readonly Type t_item;

		public override void AddToCollection(Object instance, Object item)
		{
			if (item is SysObj)
				return;

			Type Ti = sch.promote_type(item.GetType());
			PropertyInfo pi;
			if (item is Task && (pi = Ti.GetProperty("Result")) != null)
				item = pi.GetValue(item);

			if (t_item.IsAssignableFrom(Ti))
				base.AddToCollection(instance, item);

			xt.st.MultiAddMethod.Invoke(instance, new[] { item });
		}
	};
}
